#include "common.h"
#include "sym.h"
#include "syntax-tree.h"
#include "syntax.tab.h"
#include "op-list.h"

#ifdef DEBUG
#define _standard_chk(x) \
    do{\
        Assert(now&&(x),"Unexpected Type %d at line %d",now->type,now->first_line);\
        printf("%d %d %d\n",now->type,now->first_line,now->rule);\
    }while(0)
#else
#define _standard_chk(x) Assert(now&&(x),"Unexpected Type %d at line %d",now->type,now->first_line)
#endif
#define standard_chk(x) _standard_chk(now->type==x)

sym_unit var_list[1024];
typedef struct func_list{
    int first_line;
    sym_unit pos;
    struct func_list * nxt;
}func_list;
func_list * first;
static int dep=1,in_struct=0,in_func=0;
static Operand const_0,const_1;

typedef struct{
    Type type;
    int is_addr;
    Operand x;
}Exp_type;

static Exp_type formalize(Exp_type input){
    if(input.is_addr){
        Operand x=wrap_op1(new_var(),VARIABLE);
        op_pb(TOADDR,x,input.x,NULL,0);
        input.x=x;
        input.is_addr=0;
    }
    return input;
}

void treenode_scan(tree_node * now,Type ret_type);

static Type DefList(tree_node * now,Type all);
static Exp_type Exp(tree_node * now,int is_left,Operand label1,Operand label2);
static void CompSt(tree_node * now,Type ret_type,int flag);

void semantic_pre_work(){
    const_0=wrap_op1(0,CONSTANT);
    const_1=wrap_op1(1,CONSTANT);
}

static char * dig_name(tree_node * now){
    if(!now->not_null) return NULL;
    now=now->ch[0];
    return now->data.type_name;
}

Type struct_deal(tree_node * now){
    standard_chk(CYF_StructSpecifier);
    if(now->rule==1){
        var_list[++dep]=NULL;
        in_struct++;
        Type ret=DefList(now->ch[3],NULL);
        if(!ret){
            ret=malloc(sizeof(struct Type_));
            ret->kind=STRUCTURE;
            ret->u.structure=NULL;
        }
        sym_erase(var_list[dep--]);
        in_struct--;
        char * name=dig_name(now->ch[1]);
        sym_unit x=sym_insert(name,structdef,ret,NULL,1,0);
        if(!x) print_error("16",now->first_line,"Confilct def of struct");
        return x?x->type:Type_error;
    }else{
        const char * name=dig_name(now->ch[1]);
        sym_unit ret=sym_find(name);
        if(!ret||ret->kind!=structdef){
            print_error("17",now->first_line,"Undefined structure");
            return Type_error;
        }
        return ret->type;
    }
}

Type specifier_deal(tree_node * now){
    standard_chk(CYF_Specifier);
    tree_node * ch=now->ch[0];
    if(now->rule==1) return ch->data.type_int==1?Type_int:Type_float;
    if(now->rule==2) return struct_deal(ch);
    return Type_error;
}

static sym_unit VarDec(tree_node * now,Type type){
    standard_chk(CYF_VarDec);
    if(now->rule==1){
        sym_unit ret=sym_insert(dig_name(now),var,type,NULL,dep,0);
        if(!ret){
            if(in_struct) print_error("15",now->first_line,"multiple definiton in one struct");
            else print_error("3",now->first_line,"Conflict definition of variable");
            return NULL;
        }
        if(!in_struct){
            Operand x=ret->xx;
            if(in_func){
                op_pb(PARAM,x,NULL,NULL,0);
                ret->valid=1;
            }else if(type->kind!=BASIC){
                op_pb(DEC,x,NULL,NULL,type->size);
                ret->xx=wrap_op1(new_var(),VARIABLE);
                op_pb(GTADDR,ret->xx,x,NULL,0);
                ret->valid=1;
            } 
        }
        ret->nxt2=var_list[dep];
        var_list[dep]=ret;
        return ret;
    }
    if(now->rule<0) return NULL;
    int num=now->ch[2]->data.type_int;
    type=arraytype_build(type,num);
    return VarDec(now->ch[0],type);
}

static void ExtDecList(tree_node * now,Type type){
    standard_chk(CYF_ExtDecList);
    VarDec(now->ch[0],type);
    if(now->rule==2) ExtDecList(now->ch[2],type);
    return ;
}

static sym_unit ParamDec(tree_node * now){
    standard_chk(CYF_ParamDec);
    Type type=specifier_deal(now->ch[0]);
    return VarDec(now->ch[1],type);
}

static TypeList VarList(tree_node * now){
    standard_chk(CYF_VarList);
    sym_unit x=ParamDec(now->ch[0]);
    TypeList nxt=NULL;
    if(now->rule==1) nxt=VarList(now->ch[2]);
    return x?typeListBuild(x->type,nxt,1):nxt;
}

static void FuncDec(tree_node * now,Type type,int valid){
    standard_chk(CYF_FunDec);
    char * funcname=now->ch[0]->data.type_name;

    Operand x=wrap_op2(funcname,NAME);
    op_pb(FUNC,x,NULL,NULL,0);

    in_func++;
    TypeList arg=NULL;
    if(now->rule==1) arg=VarList(now->ch[2]);
    in_func--;
    x->offset=0;

    sym_unit ret=sym_insert(funcname,func,type,arg,dep-1,valid);
    ret->xx=x;
    if(!ret){
        if(error_no) print_error("4",now->first_line,"multiple definition of func");
        else print_error("19",now->first_line,"confict definition of same function");
    } else {
        func_list * xx=malloc(sizeof(func_list));
        xx->first_line=now->first_line;
        xx->pos=ret;
        xx->nxt=first;
        first=xx;
    }
}

static void ExtDef(tree_node * now){
    standard_chk(CYF_ExtDef);
    if(now->rule<0) return;
    Type type=specifier_deal(now->ch[0]);
    if(now->rule==1) ExtDecList(now->ch[1],type);
    if(now->rule==3||now->rule==4){
        var_list[++dep]=NULL;
        FuncDec(now->ch[1],type,now->rule==3);
        if(now->rule==3) CompSt(now->ch[2],type,1);
        else sym_erase(var_list[dep--]);
    }
    return;
}

static sym_unit Dec(tree_node * now,Type type){
    standard_chk(CYF_Dec);
    sym_unit x=VarDec(now->ch[0],type);
    if(now->rule==2){
        if(in_struct){
            print_error("15",now->first_line,"intialize in struct");
        }
        Exp_type e=Exp(now->ch[2],0,NULL,NULL);
        if(x&&!cmp_type(x->type,e.type)){
            print_error("5",now->first_line,"Unmatch type around equation");
            return NULL;
        }
        if(x){
            e=formalize(e);
            op_pb(ASSIGN,x->xx,e.x,NULL,0);
            x->valid=1;
        }
    }
    return x;
}

static Type DecList(tree_node * now,Type type,Type all){
    standard_chk(CYF_DecList);
    sym_unit x=Dec(now->ch[0],type);
    if(x&&in_struct) all=struct_build(all,x->name,x->type);
    if(now->rule==2) all=DecList(now->ch[2],type,all);
    return all;
}

static Type Def(tree_node * now,Type all){
    standard_chk(CYF_Def);
    if(now->rule==1){
        Type type=specifier_deal(now->ch[0]);
        all=DecList(now->ch[1],type,all);
    }
    return all;
}

static Type DefList(tree_node * now,Type all){
    standard_chk(CYF_DefList);
    if(now->rule==1) return all;
    all=Def(now->ch[0],all);
    return DefList(now->ch[1],all);
}

static inline int chk_type(Type type){return type->kind==BASIC;}

typedef struct operand_list_ * operand_list;
struct operand_list_{
    Operand x;
    operand_list nxt;
};

static TypeList Args(tree_node * now,operand_list * x,int * num){
    standard_chk(CYF_Args);
    Exp_type e=Exp(now->ch[0],0,NULL,NULL);
    if(e.type->kind==BASIC) e=formalize(e);
    Type type=e.type;
    TypeList nxt=NULL;
    operand_list temp=malloc(sizeof(struct operand_list_));
    temp->x=e.x;temp->nxt=*x;*x=temp;
    ++*num;
    if(now->rule==1) nxt=Args(now->ch[2],x,num);
    return typeListBuild(type,nxt,0);
}

static inline void deal_label(Exp_type x,Operand l1,Operand l2){
    x=formalize(x);
    if(l1) {
        op_pb(OP_IF,x.x,const_0,l1,NE);
        if(l2) op_pb(GOTO,l2,NULL,NULL,0);
    }else op_pb(OP_IF,x.x,const_0,l2,EQ);
    return;
}

static Exp_type Exp(tree_node * now,int is_left,Operand label1,Operand label2){
    standard_chk(CYF_Exp);
    if(is_left&&!(now->rule>=14&&now->rule<=16)){
        print_error("6",now->first_line,"right_exp in the left");
    }

    tree_node * ch0=now->ch[0],*ch1=now->ch[1],*ch2=now->ch[2];
    Exp_type type1,type2,ret;
    sym_unit x;
    Operand l1,l2;
    int flag=(label1)||(label2);
    switch(now->rule){
        case 1:
            type1=Exp(ch0,1,NULL,NULL);type2=Exp(ch2,0,NULL,NULL);
            if(!cmp_type(type1.type,type2.type)){
                print_error("5",now->first_line,"Unmatched assignment");
                return type2;
            }
            if(type1.type->kind==ARRAY&&type2.type->kind==ARRAY){
                ret.x=type2.x;
                ret.is_addr=1;
                ret.type=type2.type;
                Operand temp=wrap_op1(new_var(),VARIABLE);
                unsigned all=type1.type->size;
                if(all>type2.type->size) all=type2.type->size;
                for(int i=0;i<all;i+=4){
                    op_pb(TOADDR,temp,type2.x,NULL,0);
                    op_pb(ASSADDR,type1.x,temp,NULL,0);
                    if(i<all-4){
                        op_pb(ADD,type1.x,type1.x,wrap_op1(4,CONSTANT),0);
                        op_pb(ADD,type2.x,type2.x,wrap_op1(4,CONSTANT),0);
                    }
                }
                if(all>4){
                    op_pb(SUB,type1.x,type1.x,wrap_op1(all-4,CONSTANT),0);
                    op_pb(SUB,type2.x,type2.x,wrap_op1(all-4,CONSTANT),0);
                }
                return ret;
            }
            type2=formalize(type2);
            if(type1.is_addr) op_pb(ASSADDR,type1.x,type2.x,NULL,0);
            else op_pb(ASSIGN,type1.x,type2.x,NULL,0);
            if(flag) deal_label(type2,label1,label2); 
            return type2;
        case 2: 
            if(flag){
                if(!label2){
                    label2=wrap_op1(new_label(),LABEL);
                    Exp(ch0,0,NULL,label2);
                    Exp(ch2,0,label1,NULL);
                    op_pb(LABL,label2,NULL,NULL,0);
                }else{
                    Exp(ch0,0,NULL,label2);
                    Exp(ch2,0,label1,label2);  
                }
                return ret.type=Type_int,ret.is_addr=0,ret.x=const_0,ret;
            }
            l1=wrap_op1(new_label(),LABEL);
            l2=wrap_op1(new_label(),LABEL);
            Exp(ch0,0,NULL,l1);
            ret.x=wrap_op1(new_var(),VARIABLE);
            ret.type=Type_int;
            ret.is_addr=0;
            type2=formalize(Exp(ch2,0,NULL,l1));
            op_pb(ASSIGN,ret.x,const_1,NULL,0);
            op_pb(GOTO,l2,NULL,NULL,0);
            op_pb(LABL,l1,NULL,NULL,0);
            op_pb(ASSIGN,ret.x,const_0,NULL,0);
            op_pb(LABL,l2,NULL,NULL,0);
            return ret;
        case 3:
            if(flag){
                if(!label1){
                    label1=wrap_op1(new_label(),LABEL);
                    Exp(ch0,0,label1,NULL);
                    Exp(ch2,0,NULL,label2);
                    op_pb(LABL,label1,NULL,NULL,0);
                }else{
                    Exp(ch0,0,label1,NULL);
                    Exp(ch2,0,label1,label2);  
                }
                return ret.type=Type_int,ret.is_addr=0,ret.x=const_0,ret;
            }
            l1=wrap_op1(new_label(),LABEL);
            l2=wrap_op1(new_label(),LABEL);
            Exp(ch0,0,l1,NULL);
            ret.x=wrap_op1(new_var(),VARIABLE);
            ret.type=Type_int;
            ret.is_addr=0;
            Exp(ch2,0,l1,NULL);
            op_pb(ASSIGN,ret.x,const_0,NULL,0);
            op_pb(GOTO,l2,NULL,NULL,0);
            op_pb(LABL,l1,NULL,NULL,0);
            op_pb(ASSIGN,ret.x,const_1,NULL,0);
            op_pb(LABL,l2,NULL,NULL,0);
            return ret;
        case 4:
            type1=formalize(Exp(ch0,0,NULL,NULL));
            type2=formalize(Exp(ch2,0,NULL,NULL));
            if((type1.type!=type2.type||!chk_type(type1.type))&&!(type1.type==Type_error||type2.type==Type_error))
                print_error("7",now->first_line,"Unmatched operation");
            if(flag){
                if(label1){
                    op_pb(OP_IF,type1.x,type2.x,label1,ch1->data.type_int);
                    if(label2) op_pb(GOTO,label2,NULL,NULL,0);
                }else op_pb(OP_IF,type1.x,type2.x,label2,ch1->data.type_int^1);
                return ret.type=Type_int,ret.is_addr=0,ret.x=const_0,ret;
            }
            ret.x=wrap_op1(new_var(),VARIABLE);
            ret.type=Type_int;
            ret.is_addr=0;
            l1=wrap_op1(new_label(),LABEL);
            l2=wrap_op1(new_label(),LABEL);
            op_pb(OP_IF,type1.x,type2.x,l1,ch1->data.type_int);
            op_pb(ASSIGN,ret.x,const_0,NULL,0);
            op_pb(GOTO,l2,NULL,NULL,0);
            op_pb(LABL,l1,NULL,NULL,0);
            op_pb(ASSIGN,ret.x,const_1,NULL,0);
            op_pb(LABL,l2,NULL,NULL,0);
            return ret;
        case 5:case 6:case 7:case 8:
            type1=formalize(Exp(ch0,0,NULL,NULL));
            type2=formalize(Exp(ch2,0,NULL,NULL));
            if((type1.type!=type2.type||!chk_type(type1.type))&&!(type1.type==Type_error||type2.type==Type_error)){
                print_error("7",now->first_line,"Unmatched operation 5-8");
                ret.type=Type_error;
            }else ret.type=type1.type;
            ret.x=wrap_op1(new_var(),VARIABLE);
            ret.is_addr=0;
            op_pb(ADD+now->rule-5,ret.x,type1.x,type2.x,0);
            if(flag) deal_label(ret,label1,label2);
            return ret;
        case 9: return Exp(ch1,0,label1,label2);
        case 10: 
            if(flag) {
                type1=formalize(Exp(ch1,0,label1,label2));
                return ret.type=Type_int,ret.is_addr=0,ret.x=const_0,ret;
            }
            type1=formalize(Exp(ch1,0,NULL,NULL));
            if(!chk_type(type1.type)){
                print_error("7",now->first_line,"Unmatched operation10");
                ret.type=Type_error;
            }else ret.type=type1.type;
            ret.x=wrap_op1(new_var(),VARIABLE);
            ret.is_addr=0;
            op_pb(SUB,ret.x,const_0,type1.x,0);
            return ret;
        case 11: 
            if(flag) {
                type1=formalize(Exp(ch1,0,label2,label1));
                return ret.type=Type_int,ret.is_addr=0,ret.x=const_0,ret;
            }
            type1=formalize(Exp(ch1,0,NULL,NULL));
            if(type1.type!=Type_int) 
                print_error("7",now->first_line,"Unmatched operation11");
            
            ret.x=wrap_op1(new_var(),VARIABLE);
            ret.type=Type_int;
            ret.is_addr=0;
            l1=wrap_op1(new_label(),LABEL);
            l2=wrap_op1(new_label(),LABEL);
            op_pb(OP_IF,type1.x,const_0,l1,NE);
            op_pb(ASSIGN,ret.x,const_1,NULL,0);
            op_pb(GOTO,l2,NULL,NULL,0);
            op_pb(LABL,l1,NULL,NULL,0);
            op_pb(ASSIGN,ret.x,const_0,NULL,0);
            op_pb(LABL,l2,NULL,NULL,0);
            return ret;
        case 12:
        case 13:
            x=sym_find(dig_name(now));
            if(!x||x->kind==structdef){
                print_error("2",now->first_line,"Undefined function");
                ret.type=Type_error;
            }else 
            if(x->kind!=func){
                print_error("11",now->first_line,"() for non-func");
                ret.type=Type_error;
            } else ret.type=x->type;
            TypeList arg=NULL,arg2;
            operand_list arglist=NULL;
            int num_of_param=0,is_write=strcmp(x->name,"write");
            if(now->rule==12) arg=Args(ch2,&arglist,&num_of_param);
            if(!cmp_arg(arg,x->arg))
                print_error("9",now->first_line,"unmatched function args");
            for(;arg;arg=arg2){
                arg2=arg->nxt;
                free(arg);
            }
            for(operand_list ff=arglist;arglist;arglist=ff,num_of_param--){
                op_pb(is_write?ARG:WRITE,ff->x,NULL,NULL,num_of_param);
                ff=arglist->nxt;
                free(arglist);
            }
            ret.x=wrap_op1(new_var(),VARIABLE);
            ret.is_addr=0;
            if(strcmp(x->name,"read")==0) op_pb(READ,ret.x,NULL,NULL,0);
            else if(!is_write) op_pb(ASSIGN,ret.x,const_0,NULL,0);
            else op_pb(CALL,ret.x,x->xx,NULL,0);
            if(flag) deal_label(ret,label1,label2);
            return ret;
        case 14:
            type1=Exp(ch0,0,NULL,NULL);
            if(type1.type->kind!=ARRAY){
                print_error("10",now->first_line,"[] for not array");
                ret.type=Type_error;
                return ret;
            }
            Assert(type1.is_addr,"ARRAY NOT ADDR!");
            type2=formalize(Exp(ch2,0,NULL,NULL));
            if(type2.type!=Type_int)
                print_error("12",now->first_line,"Index not int");
            
            ret.type=type1.type->u.array.elemnt;
            ret.is_addr=1;
            Operand array_temp;
            if(type2.x->kind==CONSTANT){
                unsigned t=type2.x->u.value;
                free(type2.x);
                if(t==0) {
                    ret.x=type1.x;
                    if(flag) deal_label(ret,label1,label2);
                    return ret;
                }
                array_temp=wrap_op1(ret.type->size*t,CONSTANT);
            }else {
                array_temp=wrap_op1(new_var(),VARIABLE);
                op_pb(MUL,array_temp,type2.x,wrap_op1(ret.type->size,CONSTANT),0);
            }
            ret.x=wrap_op1(new_var(),VARIABLE);
            op_pb(ADD,ret.x,type1.x,array_temp,0);
            if(flag) deal_label(ret,label1,label2);
            return ret;
        case 15:
            type1=Exp(ch0,0,NULL,NULL);const char * name=ch2->data.type_name;
            if(type1.type->kind!=STRUCTURE){
                print_error("13",now->first_line,"Not structure visit");
                return ret.type=Type_error,ret;
            }
            Assert(type1.is_addr,"STRUCT NOT ADDR!");
            ret.type=0;ret.is_addr=1; unsigned offset=type1.type->size;
            for(FieldList now=type1.type->u.structure;now;offset-=now->type->size,now=now->nxt)
            if(strcmp(now->name,name)==0){
                offset-=now->type->size;
                ret.type=now->type;
                break;
            }
            if(!ret.type){
                print_error("14",now->first_line,"Undefine field of structure");
                ret.type=Type_error;
                offset=0;
            }
            if(offset){
                ret.x=wrap_op1(new_var(),VARIABLE);
                op_pb(ADD,ret.x,type1.x,wrap_op1(offset,CONSTANT),0);
            }
            else ret.x=type1.x;
            if(flag) deal_label(ret,label1,label2);
            return ret;
        case 16:
            x=sym_find(dig_name(now));
            if(!x||x->kind!=var){
                print_error("1",now->first_line,"Undefined variable");
                ret.type=Type_error;
            }
            x->valid=1;
            ret.type=x->type;
            ret.is_addr=(x->type->kind==BASIC?0:1);
            ret.x=x->xx;
            if(flag) deal_label(ret,label1,label2);
            return ret;
        case 17:
            ret.type=Type_int;ret.is_addr=0;
            if(!flag) ret.x=wrap_op1(ch0->data.type_int,CONSTANT);
            else ret.x=ch0->data.type_int?const_1:const_0;
            if(ret.x->u.value&&label1) op_pb(GOTO,label1,NULL,NULL,0);
            else if(!(ret.x->u.value)&&label2) op_pb(GOTO,label2,NULL,NULL,0);
            return ret;
        case 18:
            ret.type=Type_float;ret.is_addr=0;
            ret.x=wrap_op3(ch0->data.type_float,CONST_F);return ret;
        default: ret.type=Type_error; ret.is_addr=0;ret.x=const_0; return ret;
    }
}

static void Stmt(tree_node * now,Type ret_type){
    standard_chk(CYF_Stmt);
    Exp_type type;
    Operand l1,l2;
    switch(now->rule){
        case 1:if(now->ch[0]->rule<16) Exp(now->ch[0],0,NULL,NULL); return;
        case 2:treenode_scan(now->ch[0],ret_type);return;
        case 3:
            type=formalize(Exp(now->ch[1],0,NULL,NULL));
            if(!cmp_type(type.type,ret_type)) print_error("8",now->first_line,"Unmatch return type");
            op_pb(RET,type.x,NULL,NULL,0);
            return;
        case 4:
            l1=wrap_op1(new_label(),LABEL);
            type=formalize(Exp(now->ch[2],0,NULL,l1));
            if(type.type!=Type_int) print_error("7",now->first_line,"condition not int");
            Stmt(now->ch[4],ret_type);
            op_pb(LABL,l1,NULL,NULL,0);
            return;
        case 5:
            l1=wrap_op1(new_label(),LABEL);
            l2=wrap_op1(new_label(),LABEL);
            type=formalize(Exp(now->ch[2],0,NULL,l1));
            if(type.type!=Type_int) print_error("7",now->first_line,"condition not int");
            Stmt(now->ch[4],ret_type);
            op_pb(GOTO,l2,NULL,NULL,0);
            op_pb(LABL,l1,NULL,NULL,0);
            Stmt(now->ch[6],ret_type);
            op_pb(LABL,l2,NULL,NULL,0);
            return;
        case 6:
            l1=wrap_op1(new_label(),LABEL);
            l2=wrap_op1(new_label(),LABEL);
            op_pb(LABL,l1,NULL,NULL,0);
            type=Exp(now->ch[2],0,NULL,l2);
            if(type.type!=Type_int) print_error("7",now->first_line,"condition not int");
            Stmt(now->ch[4],ret_type);
            op_pb(GOTO,l1,NULL,NULL,0);
            op_pb(LABL,l2,NULL,NULL,0);
            return;
        default: break;
    }
    return;
}

static void CompSt(tree_node * now,Type ret_type,int flag){
    standard_chk(CYF_CompSt);
    if(!flag) var_list[++dep]=NULL;
    if(now->rule<0) return;
    DefList(now->ch[1],NULL);
    treenode_scan(now->ch[2],ret_type);
    sym_erase(var_list[dep--]);
    return;
}

void treenode_scan(tree_node * now,Type ret_type){
    Assert(now,"should not be NULL!");
    switch (now->type)  {
        case CYF_DefList: DefList(now,NULL);return;
        case CYF_ExtDef: ExtDef(now); return;
        case CYF_Stmt: Stmt(now,ret_type); return;
        case CYF_CompSt: CompSt(now,ret_type,0); return;
        default:break;
    }
    for(int i=0;i<now->ch_num;i++) treenode_scan(now->ch[i],ret_type);
}

void undef_func_output(){
    for(func_list * now=first;now;now=now->nxt) if(!now->pos->valid){
        print_error("18",now->first_line,"Undefined function");
        now->pos->valid=1;
    }
}
